package com.only4play.common.utils;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.Callable;

/**
 * @author liyuncong
 * @version 1.0
 * @file SFTPUtil
 * @brief SFTP工具类
 * @details SFTP工具类
 * @date 2024-03-25
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2024-03-25               liyuncong          Created
 */

@Slf4j
public class SFTPUtil {

    private ChannelSftp sftp;
    private Session session;
    private static final int CONNECT_TIME_OUT = 3000;

    // sftp 登录认证配置
    private SFTPAuthConfigure authConfigure;

    public SFTPUtil(String host, Integer port, String username, String password) {
        this.authConfigure = new SFTPAuthConfigure(host, port, username, password, null);
    }

    public SFTPUtil(String host, Integer port, String privateKey) {
        this.authConfigure = new SFTPAuthConfigure(host, port, null, null, privateKey);
    }

    public SFTPUtil(SFTPAuthConfigure authConfigure) {
        this.authConfigure = authConfigure;
    }

    public SFTPUtil() {

    }

    @NoArgsConstructor
    @AllArgsConstructor
    @Getter
    @Setter
    public static class SFTPAuthConfigure {
        // ip地址
        private String host;
        // 端口
        private Integer port;
        // 登录用户名
        private String username;
        // 登录密码
        private String password;
        // 私钥
        private String privateKey;
    }

    private void doLogin() {
        try {
            String host = authConfigure.host;
            Integer port = authConfigure.port;
            String username = authConfigure.username;
            String password = authConfigure.password;
            String privateKey = authConfigure.privateKey;

            JSch jsch = new JSch();
            if (StringUtils.isNotEmpty(privateKey)) {
                // 设置私钥
                jsch.addIdentity(privateKey);
            }

            // 如果session、channel不为空，说明已经登录过
            if (Objects.nonNull(session) && Objects.nonNull(sftp)) {
                return;
            }

            session = jsch.getSession(username, host, port);

            if (StringUtils.isNotEmpty(password)) {
                session.setPassword(password);
            }
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");

            session.setConfig(config);
            session.connect(CONNECT_TIME_OUT);

            Channel channel = session.openChannel("sftp");
            channel.connect();

            sftp = (ChannelSftp) channel;
        } catch (JSchException e) {
            log.error("sftp服务器登录失败, 失败信息为: {}", e.getMessage());
            throw new RuntimeException(e);
        }
    }

    private void doLogout() {
        if (Objects.nonNull(sftp)) {
            if (sftp.isConnected()) {
                sftp.disconnect();
            }
        }
        if (Objects.nonNull(session)) {
            if (session.isConnected()) {
                session.disconnect();
            }
        }
    }

    private boolean doAnythings(Callable<Boolean> callable) {
        try {
            doLogin();
            return callable.call();
        } catch (Exception exception) {
            throw new RuntimeException(exception);
        } finally {
            doLogout();
        }
    }

    public boolean upload(String sftpArchivePath, String fileName, byte[] data) throws Exception {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
        return upload(sftpArchivePath, fileName, inputStream);
    }

    public boolean upload(String sftpArchivePath, String fileName, File file) throws Exception {
        InputStream inputStream = Files.newInputStream(file.toPath());
        return upload(sftpArchivePath, fileName, inputStream);
    }

    public boolean upload(String sftpArchivePath, String fileName, InputStream inputStream) throws Exception {
        return doAnythings(() -> {
            try {
                sftp.cd(sftpArchivePath);
            } catch (SftpException e) {
                sftp.mkdir(sftpArchivePath);
                sftp.cd(sftpArchivePath);
            }
            sftp.put(inputStream, fileName);
            return true;
        });
    }

    public boolean download(String sftpArchivePath, String fileName, String downloadSavePath) throws Exception {
        OutputStream outputStream = Files.newOutputStream(Path.of(downloadSavePath));
        return download(sftpArchivePath, fileName, outputStream);
    }

    public boolean download(String sftpArchivePath, String fileName, byte[] downloadedBytes) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        download(sftpArchivePath, fileName, outputStream);
        downloadedBytes = outputStream.toByteArray();
        return true;
    }

    public boolean download(String sftpArchivePath, String fileName, OutputStream outputStream) throws Exception {
        return doAnythings(() -> {
            sftp.cd(sftpArchivePath);
            sftp.get(fileName, outputStream);
            return true;
        });
    }

    public boolean delete(String sftpArchivePath, String fileName) {
        return doAnythings(() -> {
            sftp.cd(sftpArchivePath);
            sftp.rm(fileName);
            return false;
        });
    }

    /**
     * 检查文件是否存在
     *
     * @param sftpArchivePath sftp服务器归档路径
     * @param filename        文件名
     * @return 检查结果
     */
    public boolean isExist(String sftpArchivePath, String filename) {
        return doAnythings(() -> {
            try {
                sftp.cd(sftpArchivePath);
                sftp.stat(filename);
                return true;
            } catch (Exception exception) {
                return false;
            }
        });
    }

    /**
     * 检查路径是否存在
     *
     * @param sftpArchivePath 文件路径
     * @return 检查结果
     */
    public boolean isExist(String sftpArchivePath) {
        return doAnythings(() -> {
            try {
                sftp.stat(sftpArchivePath);
                return true;
            } catch (Exception exception) {
                return false;
            }
        });
    }
}
